/*
 ============================================================================
 Name        : tp2.c
 Author      : Pablo
 Version     :
 Copyright   : 
 Description : Hello World in C, Ansi-style
 ============================================================================
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "entradaSaida.h"
#include "controleEleicao.h"
#include <pthread.h>
#define LEITURA "r"
#define MAX_LINHA 999

/**
 * Estrutura para passar os parâmetros necessários a uma thread para o cálculo das instâncias
 */
typedef struct {
	int id;			//ID da thread
	int inicio;
	int fim;
	int numThreadsInternas;
	ListaVoto *lv;
} ThreadInstancia;


/**
 * Prepara a leitura dos dados do arquivo
 *
 * arq -> arquivon a ser manipulado pelo programa
 * nomeArquivo -> nome do arquivo a ser lido
 */
int iniciarLeituraArquivo(FILE **arq, char *nomeArquivo) {

	char buffer[MAX_LINHA];

	abrirArquivo(arq, nomeArquivo, LEITURA);

	lerLinha(buffer, MAX_LINHA, arq);

	int qtdInstancias = atoi(buffer);

	return qtdInstancias;
}

/*
 * Finaliza a leitura fechando o arquivo
 */
void terminarLeitura(FILE **arq) {
	fecharArquivo(arq);
}

/**
 * Preenche a lista de candidatos em uma estrutura do tipo ListaVoto
 *
 * arq -> arquivo com os dados da votação
 * lv  -> lista encadeada do tipo ListaVoto
 *
 */
void preencherCandidatos(FILE **arq, ListaVoto *lv) {

	char buffer[MAX_LINHA];

	ListaCandidato lc;
	criarListaCandidato(&lc);

	lerLinha(buffer, MAX_LINHA, arq);
	int numVotos = atoi(buffer);

	lerLinha(buffer, MAX_LINHA, arq);

	char * pch;
	pch = strtok(buffer, " ");

	int indice = 0;
	while (pch != NULL) {
		int candidato = atoi(pch);
		inserirCandidato(&lc,candidato, ++indice);
		pch = strtok(NULL, " ");
	}

	inserirVoto(lv,numVotos,lc);
}

/**
 * Realiza o preenchimento de uma lista encadeada de
 * ListaVoto com os dados do arquivo passado como parâmetro.
 *
 * arq -> arquivo com os dados da votação
 * L   -> lista encadeado do tipo ListaVoto
 *
 */
void preencherListaVoto(FILE **arq, ListaVoto *L) {

	char buffer[MAX_LINHA];

	lerLinha(buffer, MAX_LINHA, arq);

	int qtdVotos = atoi(buffer);

	int i;
	for(i = 0; i < qtdVotos; i++) {
		preencherCandidatos(arq,L);
	}

}

/**
 * Método usado na paralelização por instâncias do problema
 * Recebe como parâmetro uma estrutura do tipo ThreadInstancia que contém um vetor de lista
 * encadeada de instancias, um inicio e um fim a ser tratado pela thread
 * dentro da lista.
 */
void *thread_instancia(void *arg) {

	ThreadInstancia *incoming = (ThreadInstancia*) arg;
	int inicio = incoming->inicio;
	int fim = incoming->fim;
	int numThreadsInternas = incoming->numThreadsInternas;
	char vetor[(fim-inicio)*MAX_LINHA + 1];
	int i;

	if(fim <= inicio) {
		return (NULL);
	}

	for(i = inicio; i < fim; i++) {

		ListaVoto lv = incoming->lv[i];

		int numCandidatos = obterNumeroCandidatos(lv.Primeiro->prox->itemVoto.candidatos);
		int **matriz = criarMatriz(lv);
		int **permutacao = gerarPermutacao(lv);
		//possivel erro aki
		char *calculado = calcularResultado(permutacao,matriz,numCandidatos, numThreadsInternas);
		liberaMatriz(matriz,numCandidatos);
		liberaMatriz(permutacao,numCandidatos);
		liberaVoto(lv);
		strcat(vetor,calculado);
		if(i < fim-1) {
			strcat(vetor, "\n");
		}
	}

	return vetor;
}


/**
 * Função principal
 *
 * Recebe como único argumento o nome do arquivo com os dado da eleição
 *
 */
int main(int argc, char *argv[]) {
	char *nomeArquivo = argv[1];


	int numThreads = atoi(argv[2]);
	int numThreadsSegundoNivel = atoi(argv[3]);

	if(numThreads < 1 || numThreadsSegundoNivel < 1 ) {
		printf ("** Erro: Numero de threads invalido **\n");
		return(NULL);
	}

	FILE *arq = NULL;

	int qtdInstancias = iniciarLeituraArquivo(&arq,nomeArquivo);

	ListaVoto L[qtdInstancias];
	int i;

	int grupo = qtdInstancias / numThreads;
	int indiceGrupo = 0;

	for(i = 0; i < qtdInstancias; i++) {
		criarListaVoto(&L[i]);
		preencherListaVoto(&arq,&L[i]);
	}

	terminarLeitura(&arq);

	pthread_t thread_ID[numThreads];
	ThreadInstancia param[numThreads];
	void *exit_status = NULL;

	int cont;
	for(cont = 0; cont < numThreads; cont++ ) {
		ThreadInstancia value;
		value.id = cont;
		value.lv = L;
		value.numThreadsInternas = numThreadsSegundoNivel;

		value.inicio = indiceGrupo;

		if ( cont == (numThreads-1) )
			value.fim = qtdInstancias;
		else
			value.fim = grupo + indiceGrupo;

		indiceGrupo = value.fim;

		param[cont] = value;
	}

	for(cont = 0; cont < numThreads; cont++ ) {
		pthread_create(&thread_ID[cont] , NULL, thread_instancia , &param[cont] ) ;
	}

	for(cont = 0; cont < numThreads; cont++ ) {
		pthread_join( thread_ID[cont] , &exit_status ) ;
		if(exit_status != NULL) {
			char *saida = (char*) exit_status;
			printf("%s", saida);
			printf("\n");
		}

	}



	return EXIT_SUCCESS;
}
